package com.hmdp.Interceptor;

import cn.hutool.core.collection.CollUtil;
import com.hmdp.annotion.PreventDuplicateSubmit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * 自定义注解
 *          @Target(ElementType.METHOD) // 注解作用于方法
 *          @Retention(RetentionPolicy.RUNTIME) // 注解在运行时生效
 *          public @interface PreventDuplicateSubmit {
 *              int timeout() default 5; // 默认防重提交的时间间隔（秒）
 *          }
 *
 * DuplicateSubmitInterceptor implements HandlerInterceptor 实现handle接口
 * 重写preHandle 和 after方法
 *
 * WebMvcConfigurer InterceptorRegistry
 *      注册添加DuplicateSubmitInterceptor
 * preHandle：
 *      检测方法是否有重复提交的注解，获取token：url+表单toString的哈希
 *      加入redis，设置过期时间，判断上次是否存在检测重复提交
 *      同时开始看门狗任务对redis进行续费，避免业务阻塞导致两个重复表单进来（数据库做兜底，日志人工排查）
 *          获取一个future对象
 *          ScheduledFuture=scheduleAtFixedRate（Runnable任务） 延迟时间，周期循环的时间
 *          同时添加一个map存取当前线程池中的任务（有别的办法知道线程池有哪些任务吗：没有。只能用中间list，map存起来，或者第三方啥的）
 *
 *
 * afterCompletion
 *      检测方法是否有重复提交的注解
 *      取消线程池的任务ThreadPoolTaskScheduler（从map中取得future）
 *      future.cancel(true);
 *          true：直接中断任务，可以中断正在运行的任务
 *          false：只能中断没开始的任务，运行时不会中断
 *      renewalTasks.remove(token);
 */
@Component
@Slf4j
public class DuplicateSubmitInterceptor implements HandlerInterceptor {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ThreadPoolTaskScheduler taskScheduler;

    private final Map<String, ScheduledFuture<?>> renewalTasks = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            PreventDuplicateSubmit annotation = method.getAnnotation(PreventDuplicateSubmit.class);
            if (annotation != null) {
                log.info("开始检测表单重复提交");
                String token = generateToken(request);

                long currentTime = System.currentTimeMillis();
                String lastSubmitTimeStr = stringRedisTemplate.opsForValue().get(token);
                if (lastSubmitTimeStr != null) {
                    long lastSubmitTime = Long.parseLong(lastSubmitTimeStr);
                    if (currentTime - lastSubmitTime < annotation.timeout() * 1000L) {
                        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                        response.getWriter().write("repeat submit");
                        log.info("检测到重复提交。");
                        return false;
                    }
                }

                // 设置锁，并启动看门狗任务
                stringRedisTemplate.opsForValue().set(token, String.valueOf(currentTime), 5, TimeUnit.SECONDS);
                startRenewalTask(token);
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            PreventDuplicateSubmit annotation = method.getAnnotation(PreventDuplicateSubmit.class);
            if (annotation != null) {
                String token = generateToken(request);
                // 取消看门狗任务
                stopRenewalTask(token);
                //这里不需要删除锁，因为删了的话只是代表此次请求，无法做到检测下次重复表单的提交
                //stringRedisTemplate.delete(token);
                //log.info("释放锁：{}", token);
            }
        }
    }

    private void startRenewalTask(String token) {
        // 每隔 3 秒续租一次锁
        ScheduledFuture<?> future = taskScheduler.scheduleAtFixedRate(() -> {
            try {
                // 检查任务是否被取消
                if (Thread.currentThread().isInterrupted()) {
                    log.info("续租任务已取消，退出续租：{}", token);
                    return;
                }

                // 检查锁是否存在
                if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(token))) {
                    stringRedisTemplate.expire(token, 5, TimeUnit.SECONDS);
                    log.info("续租锁：{}", token);
                } else {
                    log.info("锁已释放，取消续租任务：{}", token);
                    stopRenewalTask(token);
                }
            } catch (Exception e) {
                // 捕获异常并记录日志
                //可能存在续费的时候被中断命令
                log.error("续租任务执行失败：{}", token, e);
            }
        }, 3000); // 初始延迟 3 秒，周期 3 秒
        renewalTasks.put(token, future);
    }

    private void stopRenewalTask(String token) {
        ScheduledFuture<?> future = renewalTasks.get(token);
        if (future != null) {
            future.cancel(true);
            renewalTasks.remove(token);
            log.info("取消续租任务：{}", token);
        }
    }

    // 生成请求的唯一标识 还可以加上表单数据to string的哈希码
    private String generateToken(HttpServletRequest request) {
        String userId = request.getParameter("userId"); // 假设请求中包含用户ID
        String requestURI = request.getRequestURI();
        return "userId" + userId + ":" + requestURI;
    }
}